// -------------------------------------------------------------------------------------
// PaletteCell.m - image cell
// Martin D. Flynn, NeXT Computer, Inc.
// You may freely copy, distribute and reuse the code in this example.
// NeXT disclaims any warranty of any kind, expressed or implied, as to its
// fitness for any particular use.
// -------------------------------------------------------------------------------------

#import "PaletteCell.h"
#import <stdlib.h>
#import <stdio.h>
#import <string.h>
#import <stdarg.h>
#import <math.h>
#import <objc/List.h>
#import <appkit/Cursor.h>
#import <appkit/View.h>
#import <appkit/Text.h>
#import <appkit/NXImage.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/nextstd.h>
#import <dpsclient/wraps.h>
#import "PaletteMatrix.h"
#import "ImagePortfolio.h"

// -------------------------------------------------------------------------------------
// delegate selectors
#define	becameSELECTED		@selector(cellDidBecomeSelected:)
#define	resignSELECTED		@selector(cellDidResignSelected:)

// -------------------------------------------------------------------------------------
#define CELLOFFSET 	((cFlags1.bordered || cFlags1.bezeled)?(cFlags1.bordered?2.0:3.0):0.0)
#define HIGHLIGHTED	((BOOL)(cFlags1.highlighted || cFlags1.state))

// -------------------------------------------------------------------------------------
@implementation PaletteCell

// -------------------------------------------------------------------------------------
// support for multiple image types
#define	maxFILETYPES	32
static char	*imageFileExtn[maxFILETYPES + 1];
static List	*imageClassList = (List*)nil;
static int	imageClassCount = 0;

/* add image class type */
+ addImageClass:classId, ...
{
	int		i;
	char	*cp;
	va_list	args;
  
	/* initialize */
	if (!imageClassList) {
		imageClassList = [[[List alloc] initCount:1] empty];
		memset(imageFileExtn, 0, sizeof(imageFileExtn));
	}
  
	/* add class object */
	[imageClassList addObject:classId];
	imageClassCount = [imageClassList count];
  
	/* add valid extensions */
	for (i = 0; imageFileExtn[i]; i++);	// find end of list
	va_start(args, classId);
	for (;;) if (cp=va_arg(args,char*)) imageFileExtn[i++]=NXCopyStringBuffer(cp); else break;
	va_end(args);
  
	return self;
}

/* return true if extension is valid */
BOOL _validExtension(char *fileName)
{
	int	i;
	char	*extn = (char*)fileEXT((char*)fileName);
	for (i = 0; imageFileExtn[i]; i++) if (!strcasecmp(extn, imageFileExtn[i])) return YES;
	return NO;
}

// -------------------------------------------------------------------------------------
// new cell methods

/* old generic new cell */
+ new
{
	return [self newIconCell];
}

/* generic new cell */
+ newIconCell
{
	self = [self newIconCell:(char*)nil];
	[titleCell setStringValueNoCopy:""];
	return self;
}

/* new cell with title */
+ newIconCell:(const char *)aString
{
	return [[self allocFromZone:NXDefaultMallocZone()] initIconCell:aString];
}

/* generic init */
- init
{
	[self initIconCell:(char*)nil];
	[titleCell setStringValueNoCopy:""];
	return self;
}

/* init with title */
- initIconCell:(const char *)aString
{

	/* init instance */
	[super initIconCell:(char*)nil];
	[self setStringValueNoCopy:""];
	[self setAlignment:NX_CENTERED];
	titleCell = [[Cell allocFromZone:[self zone]] initTextCell:aString];
	[titleCell setAlignment:NX_CENTERED];
	[self setBezeled:NO];

	/* init vars */
	cFlags1.editable = NO;
	cFlags1.selectable = NO;
	cFlags2.noWrap = YES;
	cFlags2._isLeaf = YES;
	delegate = self;
  
	imageId = (id)nil;
	smallImage = (id)nil;
	imagePath = (char*)nil;
	imageMutex = mutex_alloc();
  
	return self;
}

/* make an empty copy of this cell */
- copy
{
	NXZone	*zone = [self zone];
	id		tCell = titleCell;
	self = [super copyFromZone:zone];
	titleCell = [[tCell copyFromZone:zone] setFont:[tCell font]];
	imageMutex = mutex_alloc();
	imageId = (id)nil;
	smallImage = (id)nil;
	imagePath = (char*)nil;
	return self;
}

/* free this cell */
- free
{
	if (imagePath) free(imagePath);
	if (imageId) [imageId free];
	if (smallImage) [smallImage free];
	[titleCell free];
	mutex_free(imageMutex);
	return [super free];
}

/* set matrix owner id */
- setDelegate:anObject
{
	delegate = anObject;
	return self;
}

// -------------------------------------------------------------------------------------
// set cell size

- setCellSize:(const NXSize*)cellSize
{
	if (smallImage) { [smallImage free]; smallImage = (id)nil; }
	return self;
}

- setFont:fontId
{
	return [titleCell setFont:fontId];
}
  
// -------------------------------------------------------------------------------------
// image methods

/* load image specified in preset file name */
- loadImageFile
{
	int		i;
	id		tempId = (id)nil;

	/* validate load */
	if (!imagePath) return (id)nil;			// no file specified
	if (imageId) return imageId;			// already loaded

	/* try all image classes until we find one we like */
	for (i = 0; !tempId && (i < imageClassCount); i++) {
		int j;
		NXSize size;
		id repList = [[imageClassList objectAt:i] newListFromFile:imagePath];
		if (!repList) continue;
		if (![repList count]) { [repList free]; continue; }
		[[repList objectAt:0] getSize:&size];
		if (!size.width || !size.height) { [repList freeObjects]; [repList free]; continue; }
		tempId = [[NXImage alloc] init];
		for (j = 0; j < [repList count]; j++) [tempId useRepresentation:[repList objectAt:j]];
		[repList free];
		mutex_lock(imageMutex);
		imageId = tempId;
		mutex_unlock(imageMutex);
		break;
	}

	return tempId;
}

/* open image (may be called from non-main thread) */
- setImageFile:(const char *)filePath
{
	char	*iName;
  
	/* free prior storage (actually this should never be necessary!) */
	mutex_lock(imageMutex);
	if (imagePath ) { free(imagePath);   imagePath = (char*)nil; }
	if (imageId   ) { [imageId free];    imageId = (id)nil;      }
	if (smallImage) { [smallImage free]; smallImage = (id)nil;   }
	mutex_unlock(imageMutex);
	
	/* save filename and title */
	imagePath = NXCopyStringBuffer(filePath);
	iName = fileNAME((char*)filePath);
	[titleCell setStringValue:(const char*)iName];	// OK since it can't draw yet
	free(iName);
  
	/* open new image and make sure it was readable */
	return ([self mainThreadPerform:@selector(loadImageFile) wait:YES])?self:(id)nil;
  
}

/* return image id */
- image
{
	id	image;
	if (!imagePath) return (id)nil;
	mutex_lock(imageMutex);
	image = imageId;
	mutex_unlock(imageMutex);
	return image;
}

/* return image representation */
- imageRepresentation:(int)imageNum
{
	id		repList, image = [self image];
	if (!image) return (id)nil;
	repList = [image representationList];
	return [repList objectAt:(imageNum % [repList count])];
}

/* return path to image */
- (const char*)imagePath
{
	return imagePath;
}

/* return cell title */
- (const char*)cellTitle
{
	return [titleCell stringValue];
}

// -------------------------------------------------------------------------------------

- calcCellSize:(NXSize *)theSize inRect:(const NXRect *)aRect
{
	NXRect rect = *aRect;
	NXSize titleSize;
	theSize->width = 1.0;
	theSize->height = 1.0;
	[titleCell calcCellSize:&titleSize inRect:&rect];
	theSize->width += titleSize.width;
	theSize->height = MAX(titleSize.height, theSize->height);
	return self;
}

// -------------------------------------------------------------------------------------
// highlight cell
  
/* set selected state */
- setState:(int)flag
{
	if (![self image]) return self;	// ignore if no image in cell
	if ((!cFlags1.state && flag) || (cFlags1.state && !flag)) {
		cFlags1.state = flag? YES : NO;
		if (flag) {
			if (delegate && [delegate respondsTo:@selector(cellBecameSelected:)])
				[delegate cellBecameSelected:self];
		} else {
			if (delegate && [delegate respondsTo:@selector(cellResignedSelected:)])
				[delegate cellResignedSelected:self];
		}
	}
	return self;
}

/* highlight and select */
- highlight:(const NXRect*)cellFrame inView:controlView lit:(BOOL)flag
{
	if (![self image]) return self;	// ignore if no image in cell
	if ((!cFlags1.highlighted && flag) || (cFlags1.highlighted && !flag)) {
		cFlags1.highlighted = flag? YES : NO;
		[self setState:cFlags1.highlighted];
		[self drawSelf:cellFrame inView:controlView];
	}
	return self;
}

/* return highlight state */
- (BOOL)isHighlighted
{
	return HIGHLIGHTED;
}

/* return selected state */
- (BOOL)isSelected
{
	return (BOOL)cFlags1.state;
}
  
// -------------------------------------------------------------------------------------
// tool methods

/* resize image (only if newSize is smaller than original) (MAIN THREAD ONLY!) */
- _resizeImage:image:(int)num toSize:(NXSize*)toSize
{
	float	aspect;
	NXRect	r = { { 0.0, 0.0 }, { 0.0, 0.0 } };
	id		newImage;

	/* return if already within bounds */
	if (!image) return (id)nil;
	[image getSize:&r.size];
	if ((r.size.width <= toSize->width) && (r.size.height <= toSize->height)) return (id)nil;
  
	/* calculate new bounds */
	aspect = r.size.width / r.size.height;
	if (r.size.width  > toSize->width )		  r.size.width  = toSize->width ;
	if (r.size.height > toSize->height)		  r.size.height = toSize->height;
	if (r.size.width  > r.size.height * aspect) r.size.width  = r.size.height * aspect;
	if (r.size.height > r.size.width  / aspect) r.size.height = r.size.width  / aspect;

	/* create a new image */
	newImage = [[NXImage alloc] initSize:&r.size];
	if (!newImage) {
		NXLogError("ImagePortfolio: Unable to init scaled image");
		return (id)nil;
	}
	[newImage setFlipped:[image isFlipped]];
  
	/* draw scaled image */
	PSgsave();
	if ([newImage lockFocus]) {
		[[self imageRepresentation:num] drawIn:&r];
		[newImage unlockFocus];
	} else {
		[newImage free];
		newImage = (id)nil;
	}
	PSgrestore();

	/* return resize image */
	return newImage;
  
}
  
// -------------------------------------------------------------------------------------
// smallImage support

/* calculate bounding size for small image */
- calcImageSize:(NXSize*)imageSize forCellSize:(NXSize*)cellSize
{
	NXSize	tSize;
	NXRect	cFrame = { {0.0, 0.0}, {0.0, 0.0} };
	cFrame.size = *cellSize;
	NXInsetRect(&cFrame, 1.0, 1.0);
	[titleCell calcCellSize:&tSize inRect:&cFrame];
	cFrame.size.height -= tSize.height + 2.0;
	*imageSize = cFrame.size;
	return self;
}

// -------------------------------------------------------------------------------------
// draw methods

- drawSelf:(const NXRect *)cellFrame inView:controlView
{
	return [self drawInside:cellFrame inView:controlView];
}

- drawInside:(const NXRect *)cellFrame inView:controlView
{
	NXSize	tSize = {0.0, 0.0}, iSize;
	NXRect	rect, cFrame = *cellFrame;
	float	offset = CELLOFFSET;
	BOOL	multi = NO;
	id		image;

	/* draw border */
	if (cFlags1.bordered) { PSsetgray(NX_BLACK); NXFrameRectWithWidth(cellFrame, 1.0); } else
	if (cFlags1.bezeled ) { NXDrawGrayBezel(cellFrame, (NXRect*)nil); }
  
	/* inset rectangle and draw highlight */
	NXInsetRect(&cFrame, offset, offset);
	PSsetgray((HIGHLIGHTED)? NX_WHITE: NX_LTGRAY);
	NXRectFill(&cFrame);
	NXInsetRect(&cFrame, 1.0, 1.0);
  
	/* calc text size */
	[titleCell calcCellSize:&tSize inRect:&cFrame];

	/* resize & draw image */
	if ([self image]) {
		rect = cFrame;
		rect.size.height -= tSize.height + 2.0;
		rect.origin.y += rect.size.height;
		if ([[imageId representationList] count] > 1) multi = YES;
		if (!smallImage) smallImage = [self _resizeImage:imageId:0 toSize:&rect.size];
		image = (smallImage)? smallImage : imageId;
		[image getSize:&iSize];
		rect.origin.y -= (rect.size.height - iSize.height) / 2.0;
		rect.origin.x += (rect.size.width  - iSize.width ) / 2.0;
		[image composite:NX_SOVER toPoint:&rect.origin];
	}
  
	/* draw image title cell */
	rect = cFrame;
	rect.origin.y += rect.size.height - tSize.height - 1;
	rect.origin.x += floor((rect.size.width - tSize.width) / 2.0);
	rect.size.width  = tSize.width;
	rect.size.height = tSize.height;
	[titleCell drawSelf:&rect inView:controlView];

	return self;
}  

@end
